Plan 208: kind files under .mdsmith/kinds/ - #391
Merged
Conversation
Each YAML file at `.mdsmith/kinds/<name>.{yaml,yml}` now declares
one kind. The basename is the kind's name; the body is the full
`KindBody` (schema, rules, path-pattern, extends). Inline
`kinds.<name>:` in `.mdsmith.yml` stays a first-class source —
projects can mix file-defined and inline kinds freely. The same
name in both is a config error naming both sources so a merged
kind cannot defeat the "read one file to know one kind" property
this surface ships.
`discoverKinds` walks the directory at load time and rejects bad
basenames (`[a-z][a-z0-9-]*`), subdirectories, `.yaml`/`.yml`
extension collisions, and unknown top-level keys (strict
decoding). The merged kind layer threads through `ValidateKinds`
unchanged so the pairwise schema-source mutex and the
`extends:` cycle detector apply uniformly across sources.
Every kind body now carries a `SourcePath` populated by `Load`
(the `.mdsmith.yml` path for inline kinds, the kind-file path
for file kinds). Provenance threads that path through
`LayerEntry.SourcePath` and `ResolvedKind.SourcePath`, and
`mdsmith kinds resolve` / `kinds show` print a `defined-in` line
next to each kind they report. The JSON shape carries
`source-path` on both the body and per-resolved-kind entries.
The repository's own `.mdsmith.yml` migration is deferred:
the pinned `mdsmith` binary this repo lints itself with does
not yet support `.mdsmith/kinds/`. Migration is scheduled for
after the next release bumps the pinned version. Plan 209 is
filed as the follow-up for the parallel
`.mdsmith/conventions/<name>.yaml` slot.
https://claude.ai/code/session_01Ctz5CK1Zhz99xn4RYtmDGn
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files
☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jeduden
marked this pull request as ready for review
May 24, 2026 13:40
Contributor
There was a problem hiding this comment.
Pull request overview
Implements plan 208 by adding support for defining kinds in standalone YAML files under .mdsmith/kinds/<name>.{yaml,yml}, merging them into loaded config with strict decoding and provenance (SourcePath) that is surfaced in CLI/audit outputs.
Changes:
- Add discovery + strict parsing for
.mdsmith/kinds/*.{yaml,yml}and merge results intocfg.Kindswith collision checks. - Thread kind provenance (
SourcePath) through kind resolution and CLI/JSON output surfaces. - Add unit + integration/contract tests and update docs/plans to document and lock the new public surface.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| plan/209_convention-files.md | Adds follow-up plan for convention-per-file config. |
| plan/208_kind-files.md | Updates plan status/tasks/acceptance criteria for kind files. |
| PLAN.md | Updates plan catalog (208 in-progress; adds 209). |
| internal/config/kind_files.go | Implements .mdsmith/kinds/ discovery, strict decoding, and merge into config. |
| internal/config/kind_files_test.go | Unit tests for kind-file discovery/merge and provenance threading. |
| internal/config/load.go | Tags inline kinds with SourcePath and merges file-defined kinds during load. |
| internal/config/config.go | Adds KindBody.SourcePath field (provenance). |
| internal/config/merge.go | Ensures SourcePath is preserved when copying kinds during merge. |
| internal/config/provenance.go | Threads SourcePath through ResolvedKind and rule-layer provenance. |
| internal/kindsout/kindsout.go | Surfaces SourcePath in text + JSON outputs (defined-in, source-path). |
| internal/kindsout/kindsout_test.go | Tests for new defined-in/source-path output surfaces. |
| internal/integration/kind_file_contract_test.go | Contract tests for .mdsmith/kinds/ public surface invariants. |
| internal/integration/kind_file_equivalence_test.go | Integration test asserting diagnostics equivalence (inline vs file kind). |
| docs/reference/kind-files.md | New reference doc for kind files layout, rules, and audit surfaces. |
| docs/reference/index.md | Adds reference entry for kind files doc. |
| docs/guides/file-kinds.md | Adds “split a kind into its own file” recipe. |
| docs/development/architecture/cross-system.md | Documents .mdsmith/kinds/ as a cross-system/public surface. |
| CLAUDE.md | Updates embedded docs catalog entry to include kind files doc. |
| AGENTS.md | Updates embedded docs catalog entry to include kind files doc. |
| .github/copilot-instructions.md | Updates embedded docs catalog entry to include kind files doc. |
Comment on lines
+159
to
+164
| // SourcePath is the workspace-absolute path of the file that | ||
| // defined this kind — either `.mdsmith.yml` for inline kinds | ||
| // or `.mdsmith/kinds/<name>.{yaml,yml}` for file-defined kinds | ||
| // (plan 208). Not serialized to YAML; populated by Load. | ||
| // Provenance surfaces it as `kinds.<name> (<path>)`; CLI | ||
| // resolve/show prints it next to each kind. |
| available without going through a target file. | ||
|
|
||
| The JSON shape (`--json`) carries a | ||
| `source-path:` key on each kind body and on every |
codecov/patch reported `∅` for the Go component on PR #391 because the new helpers had uncovered defensive branches that pulled patch coverage below the project baseline. Close them by either driving the branch with a test or removing it: - `mergeKindFiles`: drop the `cfgPath == ""` early return. Load is the only caller and always passes a non-empty path. CLAUDE.md's "defensive only when you can drive it red/green" rule applies here — no test could realistically reach it. - `discoverKinds`: add tests for the non-IsNotExist ReadDir branch (plant a regular file at `.mdsmith/kinds`), the non-YAML-extension skip branch (drop a `.md` alongside), the YAML anchor/alias rejection branch, and the malformed-YAML decode branch. Each test names the offending file in the assertion so future refactors keep the file-naming contract. - `parseKindFile`: add a chmod-0000 read-error test. Skips on test users (root) where mode-0000 files remain readable; CI runs as `runner`, where the branch is exercised. - `Load`: add a propagation test so a discovery error from a bad basename surfaces as the `loading kind files` wrap on the Load surface. - `WriteBodyText` (kindsout): add a failingWriter test for the new `defined-in:` line so its error path matches the rest of the writer surface. After these, the three new helpers (`discoverKinds`, `mergeKindFiles`, `parseKindFile`) hit 100% / 100% / ~91% (the last branch covered in CI). `WriteBodyText` is back at 100%. https://claude.ai/code/session_01Ctz5CK1Zhz99xn4RYtmDGn
jeduden
added a commit
that referenced
this pull request
May 28, 2026
* Mark plan 208 as complete; PR #391 merged 2026-05-24 * Add guide: choose between inline and file-based kinds * Fold kind-form decision into file-kinds.md; drop standalone guide Addresses /code-review findings on PR #412: - Wrong Diatáxis type (was how-to, content is background/decision-aid). Folded the unique 'when to keep inline / when to lift' selection criteria into docs/guides/file-kinds.md as a new H2 above the existing 'Split a kind into its own file' section, where it sits next to the mechanics it points at. - Removed the standalone guide and its catalog rows. - Cut sections that duplicated file-kinds.md or kind-files.md: the YAML side-by-side examples (closed: false would have errored on a frontmatter-only schema), the 'what stays inline regardless' block, the comparison table, and the 'mixing both forms' example (which mislabeled this repo's inline kinds as file-based). - Dropped the stale .mdsmith.yml line-count snapshot. - Removed the 'maintenance decision, not a behavioral one' seesaw. - New section uses zero em-dashes. * Replace 'alt-tabbing' with 'switching' in file-kinds.md Global-English pass: alt-tab is a Windows/Linux phrasal-verb idiom; the plain verb 'switching' is keyboard-agnostic and reads on a first pass for non-native readers. * Address /code-review findings 1-5 on file-kinds.md - F1: replace bare KindBody with 'body keys' (host page never defines the typed term; reference page expects this guide to define it). - F2: section-end pointer now goes directly to the kind-files reference instead of the next H2 (which itself relayed there). - F3: 'A PR touching X names itself' was an idiomatic personification; rewrite as 'carries the kind name in its path'. - F4: trim the 'Split a kind' H2 opener that re-stated the same trigger the new decision-aid section above already delivered; H2 now opens with the action. - F5: 'outweighs the history isolation' used an undefined noun phrase; expand to 'outweighs the gain of isolating each kind's edit history'. --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements plan 208. A kind can now live in its own YAML file at
.mdsmith/kinds/<name>.{yaml,yml}; the basename is the kind's name and the body is the fullKindBody(schema, rules,path-pattern:,extends:).Summary
internal/config/kind_files.go— newdiscoverKinds(workspaceDir)walks.mdsmith/kinds/*.{yaml,yml}and validates basenames ([a-z][a-z0-9-]*), rejects subdirectories,.yaml/.ymlextension collisions, and unknown top-level keys (strict decoding).Load— merges file-defined kinds intocfg.Kindsand errors on dual-source name collisions, naming both sources. Inline kinds get tagged with the.mdsmith.ymlpath so every kind body now carries aSourcePath.SourcePaththroughLayerEntryandResolvedKindso audit consumers can attribute each kind layer to its defining file. The pairwise schema-source mutex and theextends:cycle detector run unchanged on the merged kinds map (file kinds may extend inline kinds and vice versa).mdsmith kinds resolve <file>printsdefined-in <path>next to each kind it reports;kinds showadds adefined-in:line; both add asource-pathJSON field.docs/reference/kind-files.md, boundaries-table row indocs/development/architecture/cross-system.md, and a "split a kind into its own file" recipe indocs/guides/file-kinds.md.kind_files_test.go(unit),kind_file_contract_test.go(contract underinternal/integration/),kind_file_equivalence_test.go(byte-equal diagnostics between inline and file kinds), plus three file-defined parallels of the existing schema-source-mutex tests.Deferred
.mdsmith.ymlis deferred until the next release bumps the pinnedmdsmithversion (the pinned binary doesn't speak.mdsmith/kinds/yet)..mdsmith/conventions/<name>.yaml.Test plan
go test ./...— all greengo tool golangci-lint run— 0 issuesgo vet ./...— cleanmdsmith check .— passes🤖 https://claude.ai/code/session_01Ctz5CK1Zhz99xn4RYtmDGn
Generated by Claude Code